from flask import Flask, request, jsonify
import pymysql
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import logging
import requests
from typing import Dict, List, Any

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = Flask(__name__)

# 数据库配置
db_config = {
    'host': 'localhost',
    'user': 'root',
    'password': '123456',
    'database': 'login'
}

# 高德地图API配置
AMAP_API_KEY = 'df460df3e4d8f9720f02fc662926bb6e'  # 替换为有效的API密钥
AMAP_API_URL = "https://restapi.amap.com/v5/place/detail"

class RecommendationError(Exception):
    """自定义推荐系统异常基类"""
    def __init__(self, message: str, original_exception: Exception=None):
        super().__init__(message)
        self.original_exception = original_exception
        logger.error(f"{message}: {str(original_exception)}" if original_exception else message)


def get_db_connection() -> pymysql.connections.Connection:
    """获取数据库连接"""
    try:
        return pymysql.connect(**db_config)
    except pymysql.Error as e:
        raise RecommendationError("数据库连接失败", e)


@app.route('/record_history', methods=['POST'])
def record_history():
    """记录用户浏览历史"""
    user_id = request.json.get('user_id')
    attraction_id = request.json.get('attraction_id')

    if not user_id or not attraction_id:
        return jsonify({'error': '缺少用户ID或景点ID'}), 400

    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute('''
                           SELECT COUNT(*) FROM history
                           WHERE user_id = %s AND attraction_id = %s
                           ''', (user_id, attraction_id))
            exists = cursor.fetchone()[0]
            if exists > 0:
                return jsonify({'message': '该景点已存在于历史记录中'}), 200

            cursor.execute('''
                           INSERT INTO history (user_id, attraction_id)
                           VALUES (%s, %s)
                           ''', (user_id, attraction_id))
            conn.commit()
    except pymysql.MySQLError as e:
        return jsonify({'error': str(e)}), 500
    finally:
        conn.close()
    return jsonify({'message': '浏览历史记录成功'}), 201


def preprocess_keywords(keywords: str) -> str:
    """预处理关键词，替换分号和竖线为空格"""
    keywords = keywords.replace(";", " ").replace("|", " ")
    return keywords


def get_attraction_info(attraction_id: str) -> Dict[str, Any]:
    """通过高德API获取景点详细信息"""
    params = {
        'key': AMAP_API_KEY,
        'id': attraction_id,
        'output': 'json'
    }
    try:
        response = requests.get(AMAP_API_URL, params=params)
        response.raise_for_status()
        data = response.json()
        if data.get('status') == '1' and data.get('info', '') == 'OK':
            pois = data.get('pois', [])
            if not pois:
                return {'error': '没有找到景点信息'}
            attraction_info = pois[0]
            biz_ext = attraction_info.get('biz_ext', {})
            cost = biz_ext.get('cost')
            open_time = biz_ext.get('open_time')
            keywords = preprocess_keywords(attraction_info.get('type', ''))
            return {
                'attraction_name': attraction_info.get('name', '未知景点'),
                'address': attraction_info.get('address', '未知地址'),
                'keywords': keywords,
                'cost': cost if cost else None,
                'open_time': open_time if open_time else None
            }
        else:
            return {'error': f"高德地图API返回错误: {data.get('info')}"}
    except requests.RequestException as e:
        return {'error': f"请求失败: {str(e)}"}


def get_popular_attractions() -> List[Dict[str, Any]]:
    """获取热门景点列表"""
    popular_attractions = [
        {"id": "B000A8Q", "name": "故宫博物院", "address": "北京市东城区景山前街4号", "keywords": "历史 文化 博物馆", "cost": "60元", "open_time": "08:30-17:00"},
        {"id": "B000A8R", "name": "长城", "address": "北京市延庆区八达岭长城", "keywords": "历史 古迹 风景", "cost": "40元", "open_time": "06:00-19:00"},
        {"id": "B000A8S", "name": "颐和园", "address": "北京市海淀区新建宫门路19号", "keywords": "皇家园林 风景", "cost": "30元", "open_time": "06:30-20:00"},
        {"id": "B000A8T", "name": "西湖", "address": "浙江省杭州市西湖区龙井路1号", "keywords": "风景 湖泊", "cost": "免费", "open_time": "全天"},
        {"id": "B000A8U", "name": "黄山", "address": "安徽省黄山市黄山区汤口镇", "keywords": "自然风光 山峰", "cost": "230元", "open_time": "06:00-18:00"}
    ]
    return popular_attractions


def content_based_recommendations(user_id: str) -> List[Dict[str, Any]]:
    """基于内容的推荐算法"""
    conn = get_db_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute('''
                SELECT attraction_id FROM history WHERE user_id = %s
            ''', (user_id,))
            history_ids = [row[0] for row in cursor.fetchall()]
    except pymysql.MySQLError as e:
        logger.error(f"获取用户历史景点失败: {str(e)}")
        return []
    finally:
        conn.close()

    if not history_ids:
        logger.info("用户没有浏览历史记录，推荐热门景点")
        return get_popular_attractions()

    all_attractions_features = []
    all_attractions_info = {}
    for history_id in history_ids:
        try:
            attraction_info = get_attraction_info(history_id)
            if 'error' not in attraction_info and attraction_info['keywords']:
                features = np.array([attraction_info['keywords'].split()])  # 确保关键词是列表
                all_attractions_features.append(features)
                all_attractions_info[history_id] = attraction_info
        except Exception as e:
            logger.error(f"获取景点{history_id}信息失败: {str(e)}")

    if not all_attractions_features:
        logger.info("没有找到有效的景点信息")
        return []

    logger.info(f"用户历史浏览景点的特征向量: {all_attractions_features}")

    # 提取关键词并计算TF-IDF特征
    keywords_list = [info['keywords'] for info in all_attractions_info.values()]
    tfidf_vectorizer = TfidfVectorizer()
    tfidf_matrix = tfidf_vectorizer.fit_transform(keywords_list)
    tfidf_features = tfidf_matrix.toarray()

    # 计算用户特征向量（历史浏览景点的平均特征向量）
    user_features = np.mean(tfidf_features, axis=0)

    logger.info(f"用户特征向量: {user_features}")

    # 计算相似度并推荐景点
    recommended_attractions = []
    for idx, (attraction_id, attraction_info) in enumerate(all_attractions_info.items()):
        similarity = cosine_similarity(user_features.reshape(1, -1), tfidf_features[idx].reshape(1, -1))[0][0]
        logger.info(f"景点 {attraction_id} 的相似度: {similarity}")
        if similarity > 0.3:  # 降低相似度阈值
            recommended_attractions.append({
                'id': attraction_id,
                'name': attraction_info['attraction_name'],
                'address': attraction_info['address'],
                'keywords': attraction_info['keywords'],
                'cost': attraction_info['cost'],
                'open_time': attraction_info['open_time']
            })

    if not recommended_attractions:
        logger.info("没有找到相似度足够的景点")
    return recommended_attractions


@app.route('/recommend_attractions', methods=['GET'])
def recommend_attractions_route():
    user_id = request.args.get('user_id')

    if not user_id:
        return jsonify({'error': '缺少用户ID'}), 400

    try:
        recommendations = content_based_recommendations(user_id)
        if not recommendations:
            return jsonify({'message': '没有找到相关景点'}), 404
        return jsonify({'推荐景点': recommendations}), 200
    except Exception as e:
        logger.error(f"推荐系统错误:{str(e)}")
        return jsonify({'error': '推荐系统出错'}), 500
if __name__ == 'main':
    app.run(debug=True)
